/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict-local
 * @format
 */

import type {ViewProps} from './ViewPropTypes';

import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
import TextAncestor from '../../Text/TextAncestor';
import ViewNativeComponent from './ViewNativeComponent';
import * as React from 'react';
import {use} from 'react';
import invariant from 'invariant'; // [Windows]
// [Windows
import type {KeyEvent} from '../../Types/CoreEventTypes';
// Windows]

export type Props = ViewProps;

type PropsWithRef = $ReadOnly<{
  ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
  ...ViewProps,
}>;

// [Windows
// $FlowFixMe - children typing
const childrenWithImportantForAccessibility = children => {
  if (children == null) {
    return children;
  }
  const updatedChildren = React.Children.map(children, child => {
    if (React.isValidElement(child)) {
      // $FlowFixMe[incompatible-use]
      if (child.props.children) {
        // $FlowFixMe[incompatible-call]
        return React.cloneElement(child, {
          accessible: false,
          children: childrenWithImportantForAccessibility(child.props.children),
        });
      } else {
        // $FlowFixMe[incompatible-call]
        return React.cloneElement(child, {accessible: false});
      }
    }
    return child;
  });
  if (updatedChildren.length === 1) {
    return updatedChildren[0];
  } else {
    return updatedChildren;
  }
};
// Windows]

/**
 * The most fundamental component for building a UI, View is a container that
 * supports layout with flexbox, style, some touch handling, and accessibility
 * controls.
 *
 * @see https://reactnative.dev/docs/view
 */
function View(props: PropsWithRef): React.Node {
  const hasTextAncestor = use(TextAncestor);

  // Extract common props needed by all paths
  const {
    accessibilityElementsHidden,
    accessibilityLabel,
    accessibilityLabelledBy,
    accessibilityLevel, // Windows
    accessibilityDescription, //Windows
    accessibilityLiveRegion,
    accessibilityPosInSet, // Windows
    accessibilitySetSize, // Windows
    accessibilityState,
    accessibilityValue,
    'aria-busy': ariaBusy,
    'aria-checked': ariaChecked,
    'aria-disabled': ariaDisabled,
    'aria-expanded': ariaExpanded,
    'aria-multiselectable': ariaMultiselectable, // Windows
    'aria-required': ariaRequired, // Windows
    'aria-description': ariaDescription, //Windows
    'aria-hidden': ariaHidden,
    'aria-label': ariaLabel,
    'aria-labelledby': ariaLabelledBy,
    'aria-level': ariaLevel,
    'aria-live': ariaLive,
    'aria-posinset': ariaPosinset, // Windows
    'aria-readonly': ariaReadOnly, // Windows
    'aria-selected': ariaSelected,
    'aria-setsize': ariaSetsize, // Windows
    'aria-valuemax': ariaValueMax,
    'aria-valuemin': ariaValueMin,
    'aria-valuenow': ariaValueNow,
    'aria-valuetext': ariaValueText,
    focusable,
    disabled,
    id,
    importantForAccessibility,
    nativeID,
    tabIndex,
    ref,
    ...otherProps
  } = props;

  // Pre-compute common values needed by both optimized and Paper paths
  const _accessibilityLabelledBy =
    ariaLabelledBy?.split(/\s*,\s*/g) ?? accessibilityLabelledBy;

  const _accessibilityState =
    accessibilityState != null ||
    ariaBusy != null ||
    ariaChecked != null ||
    ariaDisabled != null ||
    ariaExpanded != null ||
    ariaSelected != null ||
    ariaReadOnly != null || // Windows
    ariaMultiselectable != null || // Windows
    ariaRequired != null // Windows
      ? {
          busy: ariaBusy ?? accessibilityState?.busy,
          checked: ariaChecked ?? accessibilityState?.checked,
          disabled: ariaDisabled ?? accessibilityState?.disabled,
          expanded: ariaExpanded ?? accessibilityState?.expanded,
          selected: ariaSelected ?? accessibilityState?.selected,
          readOnly: ariaReadOnly ?? accessibilityState?.readOnly, // Windows
          multiselectable:
            ariaMultiselectable ?? accessibilityState?.multiselectable, // Windows
          required: ariaRequired ?? accessibilityState?.required, // Windows
        }
      : undefined;

  const _accessibilityValue =
    accessibilityValue != null ||
    ariaValueMax != null ||
    ariaValueMin != null ||
    ariaValueNow != null ||
    ariaValueText != null
      ? {
          max: ariaValueMax ?? accessibilityValue?.max,
          min: ariaValueMin ?? accessibilityValue?.min,
          now: ariaValueNow ?? accessibilityValue?.now,
          text: ariaValueText ?? accessibilityValue?.text,
        }
      : undefined;

  const _keyDown =
    otherProps.keyDownEvents || otherProps.onKeyDown
      ? (event: KeyEvent) => {
          if (
            otherProps.keyDownEvents &&
            event.isPropagationStopped() !== true
          ) {
            // $FlowFixMe - keyDownEvents was already checked to not be undefined
            for (const el of otherProps.keyDownEvents) {
              if (
                event.nativeEvent.code === el.code &&
                el.handledEventPhase === 3
              ) {
                event.stopPropagation();
              }
            }
          }
          otherProps.onKeyDown && otherProps.onKeyDown(event);
        }
      : undefined;

  const _keyUp =
    otherProps.keyUpEvents || otherProps.onKeyUp
      ? (event: KeyEvent) => {
          if (otherProps.keyUpEvents && event.isPropagationStopped() !== true) {
            // $FlowFixMe - keyUpEvents was already checked to not be undefined
            for (const el of otherProps.keyUpEvents) {
              if (
                event.nativeEvent.code === el.code &&
                el.handledEventPhase === 3
              ) {
                event.stopPropagation();
              }
            }
          }
          otherProps.onKeyUp && otherProps.onKeyUp(event);
        }
      : undefined;

  const _keyDownCapture =
    otherProps.keyDownEvents || otherProps.onKeyDownCapture
      ? (event: KeyEvent) => {
          if (
            otherProps.keyDownEvents &&
            event.isPropagationStopped() !== true
          ) {
            // $FlowFixMe - keyDownEvents was already checked to not be undefined
            for (const el of otherProps.keyDownEvents) {
              if (
                event.nativeEvent.code === el.code &&
                el.handledEventPhase === 1
              ) {
                event.stopPropagation();
              }
            }
          }
          otherProps.onKeyDownCapture && otherProps.onKeyDownCapture(event);
        }
      : undefined;

  const _keyUpCapture =
    otherProps.keyUpEvents || otherProps.onKeyUpCapture
      ? (event: KeyEvent) => {
          if (otherProps.keyUpEvents && event.isPropagationStopped() !== true) {
            // $FlowFixMe - keyUpEvents was already checked to not be undefined
            for (const el of otherProps.keyUpEvents) {
              if (
                event.nativeEvent.code === el.code &&
                el.handledEventPhase === 1
              ) {
                event.stopPropagation();
              }
            }
          }
          otherProps.onKeyUpCapture && otherProps.onKeyUpCapture(event);
        }
      : undefined;

  // Windows computed values
  const _focusable = tabIndex !== undefined ? !tabIndex : focusable;
  const _accessible =
    importantForAccessibility === 'no-hide-descendants'
      ? false
      : otherProps.accessible;

  if (_focusable === true && _accessible === false) {
    console.warn(
      'All focusable views should report proper accessibility information. Views marked as focusable should always be accessible.',
    );
  }

  const computedImportantForAccessibility =
    ariaHidden === true ||
    importantForAccessibility === 'no-hide-descendants' ||
    accessibilityElementsHidden === true
      ? 'no-hide-descendants'
      : importantForAccessibility;

  let actualView;
  if (ReactNativeFeatureFlags.reduceDefaultPropsInView()) {
    //Destructured props at function scope, just create processedProps
    const processedProps = otherProps as {...PropsWithRef};

    const parsedAriaLabelledBy = ariaLabelledBy?.split(/\s*,\s*/g);
    if (parsedAriaLabelledBy !== undefined) {
      processedProps.accessibilityLabelledBy = parsedAriaLabelledBy;
    }

    if (ariaLabel !== undefined) {
      processedProps.accessibilityLabel = ariaLabel;
    }

    if (ariaLive !== undefined) {
      processedProps.accessibilityLiveRegion =
        ariaLive === 'off' ? 'none' : ariaLive;
    }

    if (ariaHidden !== undefined) {
      processedProps.accessibilityElementsHidden = ariaHidden;
      if (ariaHidden === true) {
        processedProps.importantForAccessibility = 'no-hide-descendants';
      }
    }

    // Windows accessibility properties
    if (ariaLevel !== undefined) {
      processedProps.accessibilityLevel = ariaLevel;
    } else if (accessibilityLevel !== undefined) {
      processedProps.accessibilityLevel = accessibilityLevel;
    }

    if (ariaDescription !== undefined) {
      processedProps.accessibilityDescription = ariaDescription;
    } else if (accessibilityDescription !== undefined) {
      processedProps.accessibilityDescription = accessibilityDescription;
    }

    if (ariaPosinset !== undefined) {
      processedProps.accessibilityPosInSet = ariaPosinset;
    } else if (accessibilityPosInSet !== undefined) {
      processedProps.accessibilityPosInSet = accessibilityPosInSet;
    }

    if (ariaSetsize !== undefined) {
      processedProps.accessibilitySetSize = ariaSetsize;
    } else if (accessibilitySetSize !== undefined) {
      processedProps.accessibilitySetSize = accessibilitySetSize;
    }

    // Windows base properties
    if (focusable !== undefined) {
      processedProps.focusable = tabIndex !== undefined ? !tabIndex : focusable;
    } else if (tabIndex !== undefined) {
      processedProps.focusable = !tabIndex;
    }

    if (disabled !== undefined) {
      processedProps.disabled = disabled;
    }

    if (importantForAccessibility !== undefined) {
      processedProps.importantForAccessibility = importantForAccessibility;
    }

    if (nativeID !== undefined) {
      processedProps.nativeID = nativeID;
    }
    // Windows]

    if (id !== undefined) {
      processedProps.nativeID = id;
    }

    if (tabIndex !== undefined) {
      processedProps.focusable = !tabIndex;
    }

    if (
      accessibilityState != null ||
      ariaBusy != null ||
      ariaChecked != null ||
      ariaDisabled != null ||
      ariaExpanded != null ||
      ariaSelected != null ||
      ariaReadOnly != null || // Windows
      ariaMultiselectable != null || // Windows
      ariaRequired != null // Windows
    ) {
      processedProps.accessibilityState = {
        busy: ariaBusy ?? accessibilityState?.busy,
        checked: ariaChecked ?? accessibilityState?.checked,
        disabled: ariaDisabled ?? accessibilityState?.disabled,
        expanded: ariaExpanded ?? accessibilityState?.expanded,
        selected: ariaSelected ?? accessibilityState?.selected,
        readOnly: ariaReadOnly ?? accessibilityState?.readOnly, // Windows
        multiselectable:
          ariaMultiselectable ?? accessibilityState?.multiselectable, // Windows
        required: ariaRequired ?? accessibilityState?.required, // Windows
      };
    }

    if (
      accessibilityValue != null ||
      ariaValueMax != null ||
      ariaValueMin != null ||
      ariaValueNow != null ||
      ariaValueText != null
    ) {
      processedProps.accessibilityValue = {
        max: ariaValueMax ?? accessibilityValue?.max,
        min: ariaValueMin ?? accessibilityValue?.min,
        now: ariaValueNow ?? accessibilityValue?.now,
        text: ariaValueText ?? accessibilityValue?.text,
      };
    }

    // [Windows key event processing and accessible property
    if (otherProps.keyDownEvents || otherProps.onKeyDown) {
      processedProps.onKeyDown = event => {
        if (otherProps.keyDownEvents && event.isPropagationStopped() !== true) {
          // $FlowFixMe - keyDownEvents was already checked to not be undefined
          for (const el of otherProps.keyDownEvents) {
            if (
              event.nativeEvent.code === el.code &&
              el.handledEventPhase === 3
            ) {
              event.stopPropagation();
            }
          }
        }
        otherProps.onKeyDown && otherProps.onKeyDown(event);
      };
    }

    if (otherProps.keyUpEvents || otherProps.onKeyUp) {
      processedProps.onKeyUp = event => {
        if (otherProps.keyUpEvents && event.isPropagationStopped() !== true) {
          // $FlowFixMe - keyUpEvents was already checked to not be undefined
          for (const el of otherProps.keyUpEvents) {
            if (
              event.nativeEvent.code === el.code &&
              el.handledEventPhase === 3
            ) {
              event.stopPropagation();
            }
          }
        }
        otherProps.onKeyUp && otherProps.onKeyUp(event);
      };
    }

    if (otherProps.keyDownEvents || otherProps.onKeyDownCapture) {
      processedProps.onKeyDownCapture = event => {
        if (otherProps.keyDownEvents && event.isPropagationStopped() !== true) {
          // $FlowFixMe - keyDownEvents was already checked to not be undefined
          for (const el of otherProps.keyDownEvents) {
            if (
              event.nativeEvent.code === el.code &&
              el.handledEventPhase === 1
            ) {
              event.stopPropagation();
            }
          }
        }
        otherProps.onKeyDownCapture && otherProps.onKeyDownCapture(event);
      };
    }

    if (otherProps.keyUpEvents || otherProps.onKeyUpCapture) {
      processedProps.onKeyUpCapture = event => {
        if (otherProps.keyUpEvents && event.isPropagationStopped() !== true) {
          // $FlowFixMe - keyUpEvents was already checked to not be undefined
          for (const el of otherProps.keyUpEvents) {
            if (
              event.nativeEvent.code === el.code &&
              el.handledEventPhase === 1
            ) {
              event.stopPropagation();
            }
          }
        }
        otherProps.onKeyUpCapture && otherProps.onKeyUpCapture(event);
      };
    }

    // Windows accessible property
    const computedAccessible =
      processedProps.importantForAccessibility === 'no-hide-descendants'
        ? false
        : otherProps.accessible;
    if (computedAccessible !== undefined) {
      processedProps.accessible = computedAccessible;
    }

    //Windows add ref to processedProps
    if (ref !== undefined) {
      processedProps.ref = ref;
    }

    actualView = <ViewNativeComponent {...processedProps} />;
  } else {
    // Destructured props at function scope

    actualView = (
      <ViewNativeComponent
        {...otherProps}
        accessibilityLiveRegion={
          ariaLive === 'off' ? 'none' : ariaLive ?? accessibilityLiveRegion
        }
        accessibilityLabel={ariaLabel ?? accessibilityLabel}
        accessibilityLevel={ariaLevel ?? accessibilityLevel}
        accessibilityDescription={ariaDescription ?? accessibilityDescription}
        accessibilityPosInSet={ariaPosinset ?? accessibilityPosInSet}
        accessibilitySetSize={ariaSetsize ?? accessibilitySetSize}
        focusable={_focusable}
        disabled={disabled}
        accessibilityState={_accessibilityState}
        accessibilityElementsHidden={ariaHidden ?? accessibilityElementsHidden}
        accessibilityLabelledBy={_accessibilityLabelledBy}
        accessibilityValue={_accessibilityValue}
        importantForAccessibility={computedImportantForAccessibility}
        nativeID={id ?? nativeID}
        // [Windows
        accessible={_accessible}
        children={otherProps.children}
        // Windows]
      />
    );
  }

  // [Windows - Paper doesn't support Views in Text while Fabric does
  if (global.RN$Bridgeless !== true) {
    return (
      // [Windows
      // In core this is a TextAncestor.Provider value={false} See
      // https://github.com/facebook/react-native/commit/66601e755fcad10698e61d20878d52194ad0e90c
      // But since Views are not currently supported in Text, we do not need the extra provider
      <TextAncestor.Consumer>
        {consumerHasTextAncestor => {
          invariant(
            !hasTextAncestor,
            'Nesting of <View> within <Text> is not currently supported.',
          );
          return (
            <ViewNativeComponent
              {...otherProps}
              accessibilityLiveRegion={
                ariaLive === 'off'
                  ? 'none'
                  : ariaLive ?? accessibilityLiveRegion
              }
              accessibilityLabel={ariaLabel ?? accessibilityLabel}
              accessibilityLevel={ariaLevel ?? accessibilityLevel}
              accessibilityDescription={
                ariaDescription ?? accessibilityDescription
              }
              accessibilityPosInSet={ariaPosinset ?? accessibilityPosInSet}
              accessibilitySetSize={ariaSetsize ?? accessibilitySetSize}
              focusable={_focusable}
              disabled={disabled}
              accessibilityState={_accessibilityState}
              accessibilityElementsHidden={
                ariaHidden ?? accessibilityElementsHidden
              }
              accessibilityLabelledBy={_accessibilityLabelledBy}
              accessibilityValue={_accessibilityValue}
              importantForAccessibility={computedImportantForAccessibility}
              nativeID={id ?? nativeID}
              ref={ref}
              onKeyDown={_keyDown}
              onKeyDownCapture={_keyDownCapture}
              onKeyUp={_keyUp}
              onKeyUpCapture={_keyUpCapture}
              // [Windows
              accessible={_accessible}
              children={
                computedImportantForAccessibility === 'no-hide-descendants'
                  ? childrenWithImportantForAccessibility(otherProps.children)
                  : otherProps.children
              }
              // Windows]
            />
          );
        }}
      </TextAncestor.Consumer>
      // Windows]
    );
  }
  // Windows]

  // Fabric supports Views in Text, so we can use the TextAncestor context
  // [Windows
  if (hasTextAncestor) {
    return <TextAncestor value={false}>{actualView}</TextAncestor>;
  }
  // Windows]

  return actualView;
}

export default View as component(
  ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
  ...props: ViewProps
);
